#!/usr/bin/ruby
# Copyright 2013, SUSE
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#  http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'rubygems'
require 'json'
require 'getoptlong'

MAGIC_DOT = '@@ESCAPED_DOT@@'

#
# Parsing options can be added by adding to this list before calling opt_parse
#
@options = [
    [ [ '--help', '-h', GetoptLong::NO_ARGUMENT ], "--help or -h - help" ],
    [ [ '--attribute', '-a', GetoptLong::REQUIRED_ARGUMENT ], "--attribute <attribute> or -a <attribute> - Name of attribute to set. Use . as sub-attribute delimiter" ],
    [ [ '--value', '-v', GetoptLong::REQUIRED_ARGUMENT ], "--value <value> or -v <value> - Value to set for attribute" ],
    [ [ '--raw', '-r', GetoptLong::NO_ARGUMENT ], "--raw or -r - value is a raw argument already in JSON format" ],
    [ [ '--no-clobber', '-n', GetoptLong::NO_ARGUMENT ], "--no-clobber - Don't overwrite existing attributes" ],
    [ [ '--remove', GetoptLong::NO_ARGUMENT ], "--remove - Remove attribute instead of setting it" ],
]

@attribute = nil
@value = nil
@raw = false
@no_clobber = false
@remove = false

def usage (rc)
  puts "Usage: json-edit [options] <filename>"
  @options.each do |options|
    puts "  #{options[1]}"
  end
  puts "  Use '-' for <filename> to use stdin/stdout for json input/ouput."
  exit rc
end

def help
  usage 0
end


### Start MAIN ###

def opt_parse()
  sub_options = @options.map { |x| x[0] }
  opts = GetoptLong.new(*sub_options)

  opts.each do |opt, arg|
    case opt
      when '--help'
        usage 0
      when '--attribute'
        if @attribute.nil?
          @attribute = arg
        else
          usage -1
        end
      when '--value'
        if @value.nil?
          @value = arg
        else
          usage -1
        end
      when '--raw'
        @raw = true
      when '--no-clobber'
        @no_clobber = true
      when '--remove'
        @remove = true
      else
        usage -1
    end
  end

  if ARGV.length > 1
    usage -1
  end
end

def main()
  opt_parse

  begin
    if ARGV.length != 1
      usage -1
    end

    filename = ARGV[0]
    if filename == '-'
      json = JSON.parse(STDIN.read())
    elsif File.exists?(filename)
      json = JSON.parse(File.open(filename).read())
    else
      json = JSON.parse("{}")
    end

    if @raw
      real_value = JSON.parse("{\"value\": #{@value}}")["value"]
    else
      real_value = @value
    end

    # Protect already escaped periods from being interpreted as
    # element delimiters.  Notice that the 1st parameter to gsub is a
    # String not a Regexp.
    elements = @attribute.gsub('\.', MAGIC_DOT).split('.')
    elements.map! {|element| element.gsub(MAGIC_DOT, '.')}
    data = json

    elements[0, elements.length - 1].each { |element|
      if not (data.has_key?(element) && data[element].is_a?(Hash))
        if @remove
          exit 0
        end
        data[element] = {}
      end
      data = data[element]
    }

    unless @remove
      if @no_clobber and data.has_key?(elements[-1])
        puts "Not overwriting #{@attribute}"
      else
        data[elements[-1]] = real_value
      end
    else
      if data.has_key?(elements[-1])
        data.delete(elements[-1])
      else
        exit 0
      end
    end

    if filename == '-'
      puts JSON.pretty_generate(json)
    else
      File.open(filename, 'w') {|f| f.write(JSON.pretty_generate(json)) }
    end
  rescue => ex
    puts "json-edit failed: #{ex.message}"
    ex.backtrace.each { |bt_line| puts "#{bt_line}" }
    exit -1
  end

end

main
